﻿using System.Windows;
using System.ComponentModel;
using System.Threading;
using System.Diagnostics;
using System;
using Ops.Mpc.Controllers;
using System.Windows.Input;
using Ops.Mpc.Models;
using Ops.Mpc.Wcf;
using Ops.Mpc.Ioc;
using Ops.Mpc.Ioc.MessageBoxServices;


namespace Ops.Mpc.States
{
    public class AsyncAction : DependencyObject, INotifyPropertyChanged, ICommand
    {
        #region Fields & Properties
        public event PropertyChangedEventHandler PropertyChanged;
        private EventHandler _canExecuteChanged = null;
        private ActionResultCallback _actionResultCallback;
        private Timer _timer;
        private Timer _pollTimer;
        private long _startTime = 0;
        private bool _isAvailable = true;
        private bool _isExecuting = false;
        private IBusyIndicatorModel _busyIndicatorModel = null;

        public static readonly DependencyProperty VisualStateWaitProperty = DependencyProperty.Register("VisualStateWait", typeof(string), typeof(AsyncAction), null);
        public string VisualStateWait
        {
            get
            {
                return (string)this.GetValue(VisualStateWaitProperty);
            }

            set
            {
                this.SetValue(VisualStateWaitProperty, value);
            }
        }

        public static readonly DependencyProperty VisualStateNormalProperty = DependencyProperty.Register("VisualStateNormal", typeof(string), typeof(AsyncAction), null);
        public string VisualStateNormal
        {
            get
            {
                return (string)this.GetValue(VisualStateNormalProperty);
            }

            set
            {
                this.SetValue(VisualStateNormalProperty, value);
            }
        }

        public static readonly DependencyProperty BusyIndicatorModelItemNameProperty = DependencyProperty.Register("BusyIndicatorModelItemName", typeof(string), typeof(AsyncAction), null);
        public string BusyIndicatorModelItemName
        {
            get
            {
                return (string)this.GetValue(BusyIndicatorModelItemNameProperty);
            }

            set
            {
                this.SetValue(BusyIndicatorModelItemNameProperty, value);
            }
        }

        public static readonly DependencyProperty PollTimeIntervalSecondsProperty = DependencyProperty.Register("PollTimeIntervalSeconds", typeof(int), typeof(AsyncAction), new PropertyMetadata(0));
        public int PollTimeIntervalSeconds
        {
            get
            {
                return (int)this.GetValue(PollTimeIntervalSecondsProperty);
            }

            set
            {
                this.SetValue(PollTimeIntervalSecondsProperty, value);
            }
        }

        public static readonly DependencyProperty StartTimeSecondsProperty = DependencyProperty.Register("StartTimeSeconds", typeof(int), typeof(AsyncAction), new PropertyMetadata(0));
        public int StartTimeSeconds
        {
            get
            {
                return (int)this.GetValue(StartTimeSecondsProperty);
            }

            set
            {
                this.SetValue(StartTimeSecondsProperty, value);
            }
        }


        public static readonly DependencyProperty ItemNameProperty = DependencyProperty.Register("ItemName", typeof(string), typeof(AsyncAction), null);
        public string ItemName
        {
            get
            {
                return (string)this.GetValue(ItemNameProperty);
            }

            set
            {
                this.SetValue(ItemNameProperty, value);
            }
        }

        public static readonly DependencyProperty IsAvailableProperty = DependencyProperty.Register("IsAvailable", typeof(bool), typeof(AsyncAction),new PropertyMetadata(OnIsAvailableChanged));
        public bool IsAvailable
        {
            get
            {
                return (bool)this.GetValue(IsAvailableProperty);
            }

            set
            {
                this.SetValue(IsAvailableProperty,value);
            }
        }



        public static readonly DependencyProperty MinimumExecutionMillisecondsProperty = DependencyProperty.Register("MinimumExecutionMilliseconds", typeof(int), typeof(AsyncAction), new PropertyMetadata(0));
        public int MinimumExecutionMilliseconds
        {
            get
            {
                return (int)this.GetValue(MinimumExecutionMillisecondsProperty);
            }

            set
            {
                this.SetValue(MinimumExecutionMillisecondsProperty, value);
            }
        }


        public IController Controller
        {
            get;
            set;
        }

        public bool IsExecuting
        {
            get
            {
                return this._isExecuting;
            }

            private set
            {
                this._isExecuting = value;
                this.RaisePropertyChanged("IsExecuting");
            }
        }

        protected virtual bool RequiresElevatedPermissions
        {
            get
            {
                return false;
            }
        }


        #endregion

        #region Methods
        public virtual void Start()
        {
            if (!string.IsNullOrEmpty(this.BusyIndicatorModelItemName))
                this._busyIndicatorModel = this.TryGetModel<BusyIndicatorModel>(this.BusyIndicatorModelItemName);
        }

        public virtual void Stop()
        {
            this.OnLeavingState();
        }

        protected void RaisePropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

        }

        protected void SetBusyMessage(string message)
        {
            if (this._busyIndicatorModel != null)
                this._busyIndicatorModel.BusyMessage = message;
        }

        protected void SetBusyMessage(string format, params object[] parameters)
        {
            if (this._busyIndicatorModel != null)
                this._busyIndicatorModel.BusyMessage = string.Format(format, parameters);
        }
 

        #region Constructors
        public AsyncAction()
        {
            this.MakeAvailable();
        }
        #endregion

        #region Methods
 



        public bool Execute(object context, ActionResultCallback actionResultCallback)
        {
            lock (this)
            {
                Debug.WriteLine("Executing state action {0}", this.ItemName);

                if (!this.IsAvailable)
                {
                    if (actionResultCallback != null)
                        actionResultCallback.Invoke(new ActionResult() { Successful = false });

                    return false;

                }

                this.IsExecuting = true;

                this.MakeUnavailable();

                if (!this.CanExecute())
                {
                    if (actionResultCallback != null)
                        actionResultCallback.Invoke(new ActionResult() { Successful = false });

                    this.MakeAvailable();

                    return false;
                }

                this.CaptureExecutionStartTime();

                this._actionResultCallback = actionResultCallback;

                if (!string.IsNullOrEmpty(this.VisualStateWait))
                {
                    if (!this.Controller.Presenter.SetParentVisualState(this.VisualStateWait))
                        Debug.WriteLine("Could not set visual state to {0} for {1}", this.VisualStateWait, this.ItemName);
                    else
                        Debug.WriteLine("Set visual state to {0} for {1}", this.VisualStateWait, this.ItemName);
                }



                if (_busyIndicatorModel != null)
                    _busyIndicatorModel.MakeBusy(this);


                this.OnExecute(context);
                return true;


            }
        }

 
        protected virtual bool CanExecute()
        {
            return true;
        }

        protected virtual void OnExecute(object context)
        {
            
        }

        protected virtual void Finish()
        {
            this.Finish(new ActionResult() { Successful = true });
        }

        protected virtual void Finish(ActionResult actionResult)
        {
            if (this.Controller != null)
                if (!this.Controller.Presenter.Dispatcher.CheckAccess())
                {
                    this.Controller.Presenter.Dispatcher.BeginInvoke(delegate { Finish(actionResult); });
                    return;
                }


            if (this.MinimumExecutionMilliseconds != 0)
            {
                long executionLength = (long)TimeSpan.FromTicks(DateTime.Now.Ticks - this._startTime).TotalMilliseconds;

                if (executionLength < this.MinimumExecutionMilliseconds)
                {
                    Debug.WriteLine("Delaying the finish of action {0}", this.ItemName);

                    this._timer = new Timer(delegate { Finish(actionResult); }, null, this.MinimumExecutionMilliseconds - executionLength, Timeout.Infinite);
                    return;
                }

            }

            this._timer = null;

            Debug.WriteLine("Finished executing action {0}", this.ItemName);

            this.IsExecuting = false;

            this.OnFinish(actionResult);

            if (!string.IsNullOrEmpty(this.VisualStateWait))
            {
                if (!this.Controller.Presenter.SetParentVisualState(this.VisualStateNormal))
                    Debug.WriteLine("Could not set visual state to {0} for {1}", this.VisualStateNormal, this.ItemName);
                else
                    Debug.WriteLine("Set visual state to {0} for {1}", this.VisualStateNormal, this.ItemName);
            }


            if (_busyIndicatorModel != null)
                _busyIndicatorModel.MakeNotBusy(this);


            this.MakeAvailable();

            if (this._actionResultCallback != null)
                this._actionResultCallback(actionResult);

            this._actionResultCallback = null;



        }



        protected virtual void OnFinish(ActionResult stateActionResult)
        {
        }

        protected virtual void OnError(string message, Exception exception)
        {
            if (!(exception is OperationCancelledException))
            {
                IMessageBoxService iMessageBoxService = IocServiceLocator.Get<MessageBoxService>();

                if (iMessageBoxService != null)
                    iMessageBoxService.Show(message, "Error", MessageBoxButton.OK);
                else
                    MessageBox.Show(message, "Error", MessageBoxButton.OK);

            }

            this.Finish(new ActionResult() { Successful = false, Exception = exception, ExceptionMessage = message });
        }

        protected void UpdateAvailability()
        {
            this.IsAvailable = this.GetAvailability();
        }

        protected virtual bool GetAvailability()
        {
            return this._isAvailable
#if !WINDOWS_PHONE                
                && ((!this.RequiresElevatedPermissions) || Application.Current.HasElevatedPermissions)
#endif
                ;
        }

        protected void MakeAvailable()
        {
            this._isAvailable = true;
            this.UpdateAvailability();
        }

        protected void MakeUnavailable()
        {
            this._isAvailable = false;
            this.UpdateAvailability();
        }

        private void CaptureExecutionStartTime()
        {
            this._startTime = DateTime.Now.Ticks;

        }

        public void OnEnteringState()
        {
            if (this.PollTimeIntervalSeconds > 0 || this.StartTimeSeconds > 0)
            {
                if (this._pollTimer == null)
                    this._pollTimer = new Timer(delegate
                    {
                        if (!this.Controller.Presenter.Dispatcher.CheckAccess())
                        {
                            this.Controller.Presenter.Dispatcher.BeginInvoke(delegate { Execute(null,null); });
                            return;
                        } 
                        
                        Execute(null, null);
                    }, null, this.StartTimeSeconds * 1000, this.PollTimeIntervalSeconds == 0 ? Timeout.Infinite : this.PollTimeIntervalSeconds * 1000);

                else
                    this._pollTimer.Change(this.StartTimeSeconds * 1000, this.PollTimeIntervalSeconds == 0 ? Timeout.Infinite : this.PollTimeIntervalSeconds * 1000);
            }
        }

        public void OnLeavingState()
        {
            if (this._pollTimer != null)
            {
                this._pollTimer.Change(Timeout.Infinite, Timeout.Infinite);
            }

        }

        public override string ToString()
        {
            return this.ItemName;
        }

        public Model GetModel(string itemName)
        {
            return this.Controller.GetModel(itemName);
        }

        public T GetModel<T>(string itemName) where T:Model
        {
            return this.GetModel(itemName) as T;
        }

        public T GetModel<T>() where T : Model
        {
            return this.GetModel<T>(typeof(T).Name);
        }

        public T TryGetModel<T>() where T : Model
        {
            T model = null;

            try
            {
                model = this.GetModel<T>(typeof(T).Name);
            }
            catch (ModelNotFoundException)
            {
            }

            return model;
        }

        public T TryGetModel<T>(string itemName) where T : Model
        {
            T model = null;

            try
            {
                model = this.GetModel<T>(itemName);
            }
            catch (ModelNotFoundException)
            {
            }

            return model;


        }
        #endregion


        protected virtual string GetServerUri()
        {
            int index = Application.Current.Host.Source.AbsoluteUri.IndexOf("/ClientBin");

            if (index < 0)
                throw new Exception("Could not find ClientBin, you must run application from a website");

            Uri uri = new Uri(Application.Current.Host.Source.AbsoluteUri.Substring(0, index));
            return uri.AbsoluteUri;
        }

        #endregion

        #region ICommand Members

        private static void OnIsAvailableChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            AsyncAction asyncAction = dependencyObject as AsyncAction;

            if (asyncAction._canExecuteChanged != null)
                asyncAction._canExecuteChanged(asyncAction, EventArgs.Empty);

        }

        bool ICommand.CanExecute(object parameter)
        {
            return  this.IsAvailable;
        }

        event EventHandler ICommand.CanExecuteChanged
        {
            add { this._canExecuteChanged += value;  }
            remove { this._canExecuteChanged -= value; }
        }

        void ICommand.Execute(object parameter)
        {
            this.Execute(parameter, OnExecuteComplete);
        }

        private void OnExecuteComplete(ActionResult actionResult )
        {

        }
        



        #endregion
    }
}
